{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeSynonymInstances       #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# OPTIONS -Wall #-}

--------------------------------------------------------------------------------
-- |
-- Module      :  Majalan.Pitch.ET12.Base
-- Copyright   :  (c) Stephen Tetley 2014
-- License     :  BSD3
--
-- Maintainer  :  stephen.tetley@gmail.com
-- Stability   :  unstable
-- Portability :  GHC
--
-- Base types include a type to represent quarter-notes / 
-- beats.
--
--------------------------------------------------------------------------------

module Majalan.Pitch.ET12.Base
  ( 


    MidiPitch
  , Interval


  , ET12
  , makeET12

  , et12ToOctave
  , octaveToET12


  ) where

import Majalan.Base.Internal.Base
import Majalan.Base.Internal.PitchUtils

import Data.AffineSpace                         -- package: vector-space





--------------------------------------------------------------------------------
-- ET12


-- Note - this representation avoids pitch (and interval) naming
-- as we are only generating numerical values. 
-- 
-- Recovering spelling would be good for ghci...
-- 


-- | Pitch-class representation - octave and pitch-class
--
-- The Csound book calls this @cpspch@.
-- 
data ET12 = ET12 Int Z12
  deriving (Eq,Ord)

instance Show ET12 where
  showsPrec _ (ET12 o i) = shows o . showChar '.' . fn i
    where
      fn n | n < 10    = showChar '0' . shows n
           | otherwise = shows n 
 

instance TPitch ET12 where
  toHertz gblhz = octaveToHertz gblhz . realToFrac . et12ToOctave



makeET12 :: Int -> Int -> ET12
makeET12 o i = ET12 o (toZ12 i)



midiToET12 :: MidiPitch -> ET12
midiToET12 mp = ET12 (o+1) (fromIntegral i)
  where
    (o,i) = fromIntegral mp `divMod` 12

et12ToMidi :: ET12 -> MidiPitch
et12ToMidi (ET12 o i) = fromIntegral $ fromIntegral i + ove
  where
    ove = 12 * (o-1)



-- Octave should be a data type...
et12ToOctave :: ET12 -> Octave
et12ToOctave (ET12 o i) = y + (z / 12)
  where
    y = fromIntegral o  
    z = fromIntegral i





octaveToET12 :: Octave -> ET12
octaveToET12 d = ET12 (o + a) (fromIntegral i)
    where
      (o,f) = properFraction d
      i0    = round $ f * 100
      (a,i) = i0 `divMod` 12
    





pchDiff :: ET12 -> ET12 -> Interval
pchDiff a b =  et12ToMidi a .-. et12ToMidi b


-- Simplest addtion (subtraction...) is to go via an 
-- integer representation aka midi.
--
semitoneAdd :: ET12 -> Interval -> ET12
semitoneAdd pch iv = midiToET12 $ (.+^ iv) $ et12ToMidi pch



instance AffineSpace ET12 where
  type Diff ET12 = Interval
  (.-.) = pchDiff
  (.+^) = semitoneAdd


